/* =======================================================================================================
 *              <<Branchitech>> Software License v1.0
 * =======================================================================================================

 * 版权所有  (c) 2005 <<Branchitech>>

 * Copyright (c) 2005 <<Branchitech>> All rights reserved.

 * <<Branchitech>> 对由其自主开发或和他人共同开发的所有内容和服务拥有全部知识产权，

 * 此等知识产权受到适用的知识产权（版权-著作权、商标权、专利权、非专利技术等）法律

 * 和其他法律及相关国际条约的保护。

 * 未经授权许可，任何个人、单位、组织等不得对本公司的软件产品、程序（包括各种可执行文件、源码、其他文件、

 * 开发文档、技术手册等）进行编译、反编译、再编译、出售、复制、传播、破坏、仿制、非法占有等。

 * 本公司保留一切追究侵犯人法律责任之权利。其他语言之版权声明以此汉语版为准。

 * www.<<Branchitech>>.com

 * ======================================================================================================
 */
package net.zoneland.zrdp.framework.web.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import net.zoneland.uniflow.client.ReadObject;
import net.zoneland.uniflow.client.TaskObject;
import net.zoneland.uniflow.client.UserInfo;
import net.zoneland.zrdp.common.exception.BizException;
import net.zoneland.zrdp.common.utils.JacksonUtils;
import net.zoneland.zrdp.framework.manager.AsyncManager;
import net.zoneland.zrdp.framework.manager.factory.AsyncFactory;
import net.zoneland.zrdp.framework.web.domain.mapstruct.UniflowConverter;
import net.zoneland.zrdp.framework.web.domain.notice.WorkNotice;
import net.zoneland.zrdp.framework.web.domain.uniflow.MoaTaskPojo;
import net.zoneland.zrdp.framework.web.domain.uniflow.OtherParam;
import net.zoneland.zrdp.framework.web.domain.uniflow.TaskCommonPojo;
import net.zoneland.zrdp.framework.web.domain.uniflow.TaskEmailPojo;
import net.zoneland.zrdp.framework.web.service.NotifyService;
import net.zoneland.zrdp.system.domain.SysNotifyLog;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;

/**
 * 统一通知服务实现
 */
@Service
public class NotifyServiceImpl implements NotifyService {

    /** 日志 */
    private static final Logger LOGGER = LoggerFactory.getLogger(NotifyServiceImpl.class);

    /**
     * 默认的http请求头.
     */
    public static final HttpHeaders JSON_HTTP_HEADERS = new HttpHeaders();
    static {
        JSON_HTTP_HEADERS.setContentType(MediaType.APPLICATION_JSON);
    }

    /**
     * 统一通知线程池服务
     */
    @Resource(name = "unimessageThreadExecutor")
    private ExecutorService unimessageThreadExecutor;

    /**
     * 通知模式：邮件
     */
    private static final int NOTICE_MODE_EMAIL = 1;
    /**
     * 通知模式：短信
     */
    private static final int NOTICE_MODE_SMS = 2;

    /**
     * 发送类型：邮件
     */
    private static final int SENDER_TYPE_EMAIL = 2;
    /**
     * 发送类型：短信
     */
    private static final int SENDER_TYPE_SMS = 3;

    /**
     * 接收类型：userId
     */
    private static final int RECIPIENT_TYPE_USERID = 1;
    /**
     * 接收类型：邮箱地址
     */
    private static final int RECIPIENT_TYPE_EMAIL = 2;
    /**
     * 接收类型：短信
     */
    private static final int RECIPIENT_TYPE_SMS = 3;

    /**
     * 发送人邮箱地址
     */
    @Value("${unimessage.senderEmail:admin}")
    private String senderEmail;

    /**
     * 发送人手机号码
     */
    @Value("${unimessage.senderSms:10658288}")
    private String senderSms;

    /**
     * 统一通知中配置的系统ID.
     */
    @Value("${unimessage.appName}")
    private String systemId;

    /**
     * 通知ws接口.
     */
    @Value("${unimessage.endPoint}")
    private String uniMessageUrl;

    /** PC端待办工单地址. **/
    @Value("${uniflow.pc_task_url}")
    private String uniflowPcTaskUrl;

    /** PC端待阅工单地址. **/
    @Value("${uniflow.pc_read_url}")
    private String uniflowPcReadUrl;

    /** MOA端待办工单地址. **/
    @Value("${uniflow.moa_task_url}")
    private String uniflowMoaTaskUrl;

    /** MOA端待阅工单地址. **/
    @Value("${uniflow.moa_read_url}")
    private String uniflowMoaReadUrl;

    /** 发送待阅邮件标题. **/
    @Value("${uniNotice.send_read_email_title}")
    private String uniNoticeSendReadEmailTitle;

    /** 发送待阅邮件内容. **/
    @Value("${uniNotice.send_read_email_content}")
    private String uniNoticeSendReadEmailContent;

    /** 发送待办邮件标题. **/
    @Value("${uniNotice.send_task_email_title}")
    private String uniNoticeSendTaskEmailTitle;

    /** 发送待办邮件内容. **/
    @Value("${uniNotice.send_task_email_content}")
    private String uniNoticeSendTaskEmailContent;

    /**
     * rest请求模板.
     */
    @Resource
    private RestTemplate restTemplate;

    /** MOA不需要发送邮件的节点. **/
    private static final List<String> MOA_NOT_SEND_EMAIL_NODE_LIST = ImmutableList.of("demoFlow-draftEdit");
    /** 工单地址需替换的值. **/
    private static final String TEMPLATE_ID = "templateId";
    private static final String MOA_TASK_POJO = "moaTaskPojo";
    private static final String PC_PARAM = "pcParam";
    private static final String MOA_PARAM = "moaParam";

    public static final String ROLE_ADMIN = "admin"; //系统维护员

    public static final String SPLIT_DASH                   = "-";   // 横杠

    /** 外部moa工单地址默认值. **/
    private static final  String EMPTY = "";
    private static final  String TASK = "task";
    private static final  String READ = "read";
    private static final  String TODO = "todo";
    private static final  String TOREAD = "toread";
    private static final  String YES = "yes";

    /**
     * 发送待办邮件.
     *
     * @param taskObjects 待办信息.
     * @return List<TaskObject>
     **/
    @Override
    public List<TaskObject> sendTaskObjectEmail(final List<TaskObject> taskObjects) {
        try {
            if (CollectionUtils.isNotEmpty(taskObjects)) {
                final List<TaskEmailPojo> taskEmailPojos = UniflowConverter.INSTANCE.taskToTaskEmailPojoList(taskObjects);
                // 获取所有任务中的相同的属性值.（如：工单标题，流程模版，piid，nodeId 等）
                final TaskCommonPojo taskCommonPojo = new TaskCommonPojo(taskEmailPojos.get(0));
                final String applyTitle = taskCommonPojo.getApplyTitle();
                // 发送待办邮件.
                sendTaskEmail(String.format(uniNoticeSendTaskEmailTitle, applyTitle),
                    String.format(uniNoticeSendTaskEmailContent, applyTitle),
                    taskEmailPojos, taskCommonPojo, true);
            }
            return taskObjects;
        } catch (final UnsupportedEncodingException | JsonProcessingException ex) {
            throw new BizException("待办发送邮件异常，" + ex.getLocalizedMessage(), ex);
        }
    }

    /**
     * 发送待阅邮件.
     *
     * @param readObjects 待阅信息.
     * @return List<ReadObject>
     **/
    @Override
    public List<ReadObject> sendReadObjectEmail(final List<ReadObject> readObjects) {
        try {
            if (CollectionUtils.isNotEmpty(readObjects)) {
                final List<TaskEmailPojo> taskEmailPojos = UniflowConverter.INSTANCE.readToTaskEmailPojoList(readObjects);
                // 获取所有任务中的相同的属性值.（如：工单标题，流程模版，piid，nodeId 等）
                final TaskCommonPojo taskCommonPojo = new TaskCommonPojo(taskEmailPojos.get(0));
                final String applyTitle = taskCommonPojo.getApplyTitle();
                // 发送待阅邮件.
                sendTaskEmail(String.format(uniNoticeSendReadEmailTitle, applyTitle),
                    String.format(uniNoticeSendReadEmailContent, applyTitle),
                    taskEmailPojos, taskCommonPojo, false);
            }
            return readObjects;
        } catch (final UnsupportedEncodingException | JsonProcessingException ex) {
            throw new BizException("待阅发送邮件异常，" + ex.getLocalizedMessage(), ex);

        }
    }

    /**
     * 发送工单任务邮件.
     * @param title 邮件标题.
     * @param content 邮件内容.
     * @param taskEmailPojoList 待办待阅发送邮件（待办待阅信息不能同时存在）.
     * @param taskCommonPojo 任务公共项.
     * @param isTask 是否是待办. true 待办，false 待阅
     **/
    private void sendTaskEmail(final String title, final String content, final List<TaskEmailPojo> taskEmailPojoList,
                               final TaskCommonPojo taskCommonPojo, boolean isTask) throws UnsupportedEncodingException, JsonProcessingException {
        if (CollectionUtils.isNotEmpty(taskEmailPojoList)) {
            // 获取对应工单地址信息.
            final Pair<String, String> taskUrl = isTask ? Pair.of(uniflowPcTaskUrl, uniflowMoaTaskUrl) : Pair.of(uniflowPcReadUrl, uniflowMoaReadUrl);

            // 对待办待阅工单地址参数处理.
            final String templateId = taskCommonPojo.getTemplateId();
            // pc端任务工单URL.
            final String pcTaskUrl = StringUtils.replace(taskUrl.getKey(), TEMPLATE_ID, templateId);
            final String node = templateId + SPLIT_DASH + taskCommonPojo.getNodeId();
            // moa端任务工单URL.
            final String moaTaskUrl = MOA_NOT_SEND_EMAIL_NODE_LIST.contains(node)
                ? EMPTY : StringUtils.replace(taskUrl.getValue(), MOA_TASK_POJO, getMoaTaskParam(taskCommonPojo, isTask));

            // 发送邮件.
            taskEmailPojoList.stream()
                .filter(taskEmail -> {
                    // 工单拥有者.
                    final UserInfo owner = taskEmail.getOwner();
                    // 如果工单拥有者为空或者admin，则不发邮件
                    return !(Objects.isNull(owner) || StringUtils.equals(ROLE_ADMIN, owner.getUserId()));
                })
                .map(taskEmail -> {
                    // tiid不为空代表待办，为空代表待阅.
                    final String id = StringUtils.defaultIfBlank(taskEmail.getTiid(), taskEmail.getRiid());
                    return Pair.of(id, Lists.newArrayList(taskEmail.getOwner().getUserId()));
                })
                .forEach(pair -> {
                    // 设置pc与moa地址中的任务编码.
                    final String id = pair.getKey();
                    final String pcUrl = StringUtils.replace(pcTaskUrl, PC_PARAM, id);
                    final String moaUrl = StringUtils.replace(moaTaskUrl, MOA_PARAM, id);

                    // 测试环境发送邮件异常（网络不通）
                    sendEmailAndSmsNotice(pair.getValue(), title, content + pcUrl + moaUrl);
                });
        }
    }

    /**
     * 获取MOA工单参数.
     * @param taskCommonPojo 任务公共项..
     * @param isTask 是否是待办.
     * @return java.lang.String
     **/
    private String getMoaTaskParam(final TaskCommonPojo taskCommonPojo, boolean isTask)
        throws UnsupportedEncodingException, JsonProcessingException {
        final String nodeId = taskCommonPojo.getNodeId();

        // 手机端详情参数.
        final MoaTaskPojo moaTaskPojo = new MoaTaskPojo();
        moaTaskPojo.setType(isTask ? TASK : READ);
        moaTaskPojo.setStatus(isTask ? TODO : TOREAD);
        moaTaskPojo.setApprivalid(taskCommonPojo.getPiid());
        moaTaskPojo.setNode(nodeId);
        moaTaskPojo.setModuleid(taskCommonPojo.getTemplateId());
        moaTaskPojo.setOpenMoaTaskFromMessage(YES);

        // 工单其他属性.
        final OtherParam otherparam = new OtherParam();
        otherparam.setNodeName(taskCommonPojo.getNodeName());
        otherparam.setNodeId(nodeId);
        otherparam.setTiid(isTask ? MOA_PARAM : EMPTY);
        otherparam.setRiid(isTask ? EMPTY : MOA_PARAM);
        moaTaskPojo.setOtherparam(otherparam);
        return URLEncoder.encode(JacksonUtils.toJsonString(moaTaskPojo), StandardCharsets.UTF_8.name());
    }

    /**
     * 发送待办待阅邮件和短信.
     * @param users 接收人用户id 或 email
     * @param title 标题
     * @param content 内容
     */
    private void sendEmailAndSmsNotice(final List<String> users, final String title, final String content) {
        sendWorkNotice(buildWorkNotice(users, title, content, NOTICE_MODE_EMAIL, RECIPIENT_TYPE_USERID, senderEmail, SENDER_TYPE_EMAIL));
    }
    /**
     * 邮件通知，根据邮箱地址发送
     * @param emails 接收人邮箱
     * @param title 标题
     * @param content 内容
     */
    @Override
    public void emailNotifyByEmails(final List<String> emails, final String title, final String content) {
        sendWorkNotice(buildWorkNotice(emails, title, content, NOTICE_MODE_EMAIL, RECIPIENT_TYPE_EMAIL, senderEmail, SENDER_TYPE_EMAIL));
    }

    /**
     * 邮件通知，根据用户id发送
     * @param userIds 接收人用户id
     * @param title 标题
     * @param content 内容
     */
    @Override
    public void emailNotifyByUserIds(final List<String> userIds, final String title, final String content) {
        sendWorkNotice(buildWorkNotice(userIds, title, content, NOTICE_MODE_EMAIL, RECIPIENT_TYPE_USERID, senderEmail, SENDER_TYPE_EMAIL));
    }

    /**
     * 短信通知，根据手机号码发送
     * @param phones 接收人手机号码
     * @param content 内容
     */
    @Override
    public void smsNotifyByPhones(final List<String> phones, final String content) {
        sendWorkNotice(buildWorkNotice(phones, StringUtils.EMPTY, content, NOTICE_MODE_SMS, RECIPIENT_TYPE_SMS, senderSms, SENDER_TYPE_SMS));
    }


    /**
     * 短信通知，根据用户id发送
     * @param userIds 接收人用户id
     * @param content 内容
     */
    @Override
    public void smsNotifyByUserIds(final List<String> userIds, final String content) {
        sendWorkNotice(buildWorkNotice(userIds, StringUtils.EMPTY, content, NOTICE_MODE_SMS, RECIPIENT_TYPE_USERID, senderSms, SENDER_TYPE_SMS));
    }

    /**
     * 构造通知
     * @param recipients 接收人
     * @param title 标题
     * @param content 内容
     * @param noticeModel 通知模式
     * @param recipientType 接收类型
     * @param sender 发送人
     * @param senderType 发送类型
     * @return 通知信息对象
     */
    private WorkNotice buildWorkNotice(final List<String> recipients, final String title, final String content, final int noticeModel, final int recipientType, final String sender, final int senderType) {
        final WorkNotice workNotice = new WorkNotice();
        workNotice.setSourceSystemId(systemId);
        // 设置标题和内容
        workNotice.setNoticeTitle(title);
        workNotice.setNoticeContent(content);
        // 如果标题为空，则用内容当标题
        if (StringUtils.isEmpty(workNotice.getNoticeTitle())) {
            workNotice.setNoticeTitle(workNotice.getNoticeContent());
        }
        // 通知模式
        workNotice.setNoticeMode(noticeModel);
        // 接收人信息

        workNotice.setRecipients(recipients.toArray(new String[0]));
        workNotice.setRecipientType(recipientType);
        // 发送人信息
        workNotice.setSender(sender);
        workNotice.setSenderType(senderType);
        // 0为即时发送
        workNotice.setScheduleTime(0);
        return workNotice;
    }

    /**
     * 发送通知
     * @param workNotice 通知对象
     */
    private void sendWorkNotice(final WorkNotice workNotice) {
        try {
            // 创建一个线程去调用这个webService
            unimessageThreadExecutor.execute(() -> sendWorkNoticeExecute(workNotice));
        } catch(RejectedExecutionException e) {
            LOGGER.error("RejectedExecutionException, mailInfo:{}", workNotice, e);
        }
    }

    /**
     * 发送通知（执行）
     * @param workNotice 通知对象
     */
    private void sendWorkNoticeExecute(final WorkNotice workNotice) {
        try {
            final LocalDateTime startTime = LocalDateTime.now();
            final String content = JacksonUtils.toJsonString(workNotice);
            final HttpEntity<String> httpEntity = new HttpEntity<>(content, JSON_HTTP_HEADERS);
            final ResponseEntity<String> responseEntity = restTemplate.postForEntity(uniMessageUrl, httpEntity, String.class);
            final String body = responseEntity.getStatusCode() == HttpStatus.OK? responseEntity.getBody() : StringUtils.EMPTY;
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("sendWorkNotice result:[{}]", body);
            }
            final LocalDateTime endTime = LocalDateTime.now();
            final long duration = ChronoUnit.MILLIS.between(startTime, endTime);
            // 记录日志
            final SysNotifyLog notifyLog = new SysNotifyLog();
            notifyLog.setStartTime(startTime);
            notifyLog.setParameters(content);
            notifyLog.setResultText(body);
            notifyLog.setDuration(duration);
            notifyLog.setStatusCode((long)responseEntity.getStatusCode().value());
            AsyncManager.me().execute(AsyncFactory.recordNotifyLog(notifyLog));
        } catch(final JsonProcessingException e) {
            LOGGER.error("JsonProcessingException, mailInfo:{}", workNotice, e);
        }
    }

}
